#region Imported Namespaces
using System;
using System.Collections.Generic;
using System.Data;
using ICachePolicy = PHSRAG.Insight21.Policy.ICachePolicy;
using System.Data.SqlClient;
using System.Reflection;
#endregion

namespace PHSRAG.Insight21.BusinessObjects
{
    /// <summary>
    /// SmartBusinessObject derives from SaveableBusinessObject, but provides 
    /// tracking of it's own dirty state, and provides a mechanism to Validate an object 
    /// upon saving. All Set properties should call the SetDirty method to do the setting
    /// of the member variables, so Dirty state management can be maintained. The SmartBusinessObject
    /// also has an Id property and member variable to track it's object Id.
    /// </summary>
    public abstract class SmartBusinessObject : IBusinessObject
    {
        #region Private members
        private int id;
        protected bool dirty;
        protected bool isClone;
        private bool markForDelete;
        private ICachePolicy cachePolicy;
        #endregion

        #region Properties
        /// <summary>
        /// The Id property is the unique ID (Identity column) of the object. For a new
        /// object (not loaded from the Database via a DataRow), it will be zero.
        /// </summary>
        public int Id
        {
            set { id = value; }
            get { return id; }
        }

        /// <summary>
        /// Cache Key uniquely identifies this object in the cache.
        /// </summary>
        public abstract string CacheKey();

        /// <summary>
        /// IsDirty returns whether or not the object is dirty.
        /// </summary>
        public bool Dirty
        {
            set { dirty = value; }
            get { return dirty; }
        }

        /// <summary>
        /// IsClone returns whether or not the object has been cloned from another.
        /// </summary>
        public bool IsClone
        {
            set { SetDirty(ref isClone, value ); }
            get { return isClone; }
        }

        /// <summary>
        /// Marks the object for deletion which will cause the Save
        /// method to Delete the object.
        /// </summary>
        public bool MarkForDelete
        {
            get { return markForDelete; }
            set { SetDirty(ref markForDelete, value); }
        }

        /// <summary>Get/Set the cache policy</summary>
        protected ICachePolicy CachePolicy { get { return cachePolicy; } set { cachePolicy = value; } }

        #endregion

        #region Constructors
        /// <summary>
        /// Public constructor used only for NEW objects, not ones loaded from the database.
        /// Also, will set the Id to 0 in this instance.
        /// </summary>
        /// <param name="cachePolicy">cache poliCy determines where the object will be cached</param>
        public SmartBusinessObject(ICachePolicy cachePolicy)
        {
            CachePolicy = cachePolicy;
            Id = 0;
        }

        /// <summary>
        /// Public constructor used for existing objects, loaded from the database. The
        /// constructor will load each object's memebers from the appropriate columns in the DataRow.
        /// </summary>
        /// <param name="cachePolicy">cache poliCy determines where the object will be cached</param>
        /// <param name="dataRow">Row to load the object's datamembers from </param>
        public SmartBusinessObject(ICachePolicy cachePolicy, DataRow dataRow)
        {
            CachePolicy = cachePolicy;
        }
        #endregion

        #region Public Methods

        /// <summary>
        /// If the object is explicitly dirty (not just its contained objects), it is called
        /// on to persist itself.  The Dirty property is then set back to false.  If the smart business
        /// object has saveable contained objects, this method must be overriden to call Save on them.
        /// </summary>
        /// <param name="connection">connection to use to connect to the database</param>
        public virtual void Save(string connectionString)
        {
            if (IsDirty())
            {
                SqlConnection connection = new SqlConnection(connectionString);

                // create a connection and open it.
                connection.Open();

                // create a transaction on the connection to be passed around.
                SqlTransaction transaction = connection.BeginTransaction(Assembly.GetExecutingAssembly().GetName().Name);

                try
                {
                    // call Save method.
                    Save(transaction);

                    // commit the SQL transaction.
                    transaction.Commit();
                }
                catch
                {
                    // rollback the transaction if it exists.
                    if (transaction != null)
                    {
                        transaction.Rollback();
                    }
                    throw;
                }
                finally
                {

                    // do some cleanup.
                    if (connection.State != ConnectionState.Closed)
                        connection.Close();
                }
            }
            Dirty = false;
        }


        /// <summary>
        /// Creates a new SqlConnection and uses it to save the object
        /// </summary>
        /// <param name="connectionString">string containing database connection info</param>
        /// <param name="args">arguments to the Save</param>
        public void Save(SqlTransaction transaction)
        {
            if (IsDirty())
            {
                if (markForDelete)
                {
                    DeleteObject(transaction);
                }
                else
                {
                    SaveObject(transaction);
                }

                Dirty = false;
            }

        }


        /// <summary>
        /// Deletes the record represented by the object from the underlying table.
        /// </summary>
        /// <param name="connectionString">connection to use to connect to the database</param>
        public virtual void Delete(string connectionString)
        {
            SqlConnection connection = new SqlConnection(connectionString);

            // create a connection and open it.
            connection.Open();

            // create a transaction on the connection to be passed around.
            SqlTransaction transaction = connection.BeginTransaction(Assembly.GetExecutingAssembly().GetName().Name);

            try
            {
                // call Save method.
                Delete(transaction);

                // commit the SQL transaction.
                transaction.Commit();
            }
            catch
            {
                // rollback the transaction if it exists.
                if (transaction != null)
                {
                    transaction.Rollback();
                }
                throw;
            }
            finally
            {

                // do some cleanup.
                if (connection.State != ConnectionState.Closed)
                    connection.Close();
            }
        }


        /// <summary>
        /// Deletes the record represented by the object from the underlying table within the context of a SQL transaction
        /// </summary>
        /// <param name="transaction">The transaction in which the procedure to delete the record is to be executed</param>
        public void Delete(SqlTransaction transaction)
        {
            DeleteObject(transaction);
        }


        #endregion

        #region Abstract Methods
        /// <summary>
        /// it is the responsibility of each BO to determine whether or not it is dirty or not, 
        /// if it has contained objects by checking if any of them are dirty.
        /// </summary>
        /// <returns>true if it is dirty, false if not.</returns>
        public virtual bool IsDirty() { return dirty; }

        /// <summary>
        /// Any business logic required before saving an object can 
        /// be handled in the Validate method. Typically, if the object
        /// doesn't meet the business requirements, it would throw an error.
        /// </summary>
        public virtual void Validate() { }


        /// <summary>
        /// Loads the object based on some parameters
        /// </summary>
        /// <param name="connectionString">Database connection string</param>
        /// <param name="args">optional collection of parameters</param>
        public abstract void Load(string connectionString, params object[] args);

        /// <summary>
        ///  Save only the business object's data members (only those members which contribute to the 
        /// dirty state). This is called in the public Save method, if the object is determined to be 
        /// dirty and need saving.
        /// </summary>
        /// <param name="connectionString">database connection string.</param>
        /// <param name="sqlTransaction">transaction passed in to do the saving on.</param>
        protected abstract void SaveObject(SqlTransaction sqlTransaction);

        /// <summary>
        /// Deletes the object requested, and potentially any of its contained objects, if 
        /// the delete of those are implemented and called.
        /// </summary>
        /// <param name="sqlTransaction">transaction passed in to do the saving on.</param>
        protected abstract void DeleteObject(SqlTransaction sqlTransaction);


        #region IBusinessObject Members
        /// <summary>
        /// Cache this instance using the prevalent cache policy.
        /// </summary>
        public abstract void Cache();

        /// <summary>
        /// Remove this instance from the cache using the prevalent cache policy.
        /// </summary>
        public abstract void UnCache();
        #endregion
        #endregion

        #region SetDirty methods
        
        /// <summary>
        ///  Used when setting properties on an object. This generic Set method will
        ///  determine whether or not to mark the object as dirty. If the member variable 
        ///  and the new value passed in are the same, the method does nothing. Otherwise, 
        ///  if they are different, it sets the memer variable to the new value and also 
        ///  marks the object as dirty, which will come in handy at saving time.
        ///  This SetDirty method takes chars.
        /// </summary>
        /// <param name="member">the member variable's value</param>
        /// <param name="newValue">the new value </param>
        public void SetDirty(ref char member, char newValue)
        {
            if (member != newValue)
            {
                Dirty = true;
                member = newValue;
            }
        }

        /// <summary>
        ///  Used when setting properties on an object. This generic Set method will
        ///  determine whether or not to mark the object as dirty. If the member variable 
        ///  and the new value passed in are the same, the method does nothing. Otherwise, 
        ///  if they are different, it sets the memer variable to the new value and also 
        ///  marks the object as dirty, which will come in handy at saving time.
        ///  This SetDirty method takes strings.
        /// </summary>
        /// <param name="member">the member variable's value</param>
        /// <param name="newValue">the new value </param>
        public void SetDirty(ref string member, string newValue)
        {
            if (newValue == null) newValue = string.Empty;
            if (member != newValue)
            {
                Dirty = true;
                member = newValue;
            }
        }

        /// <summary>
        ///  Used when setting properties on an object. This generic Set method will
        ///  determine whether or not to mark the object as dirty. If the member variable 
        ///  and the new value passed in are the same, the method does nothing. Otherwise, 
        ///  if they are different, it sets the memer variable to the new value and also 
        ///  marks the object as dirty, which will come in handy at saving time.
        ///  This SetDirty method takes ints.
        /// </summary>
        /// <param name="member">the member variable's value</param>
        /// <param name="newValue">the new value </param>
        public void SetDirty(ref int member, int newValue)
        {
            if (member != newValue)
            {
                Dirty = true;
                member = newValue;
            }
        }

        /// <summary>
        ///  Used when setting properties on an object. This generic Set method will
        ///  determine whether or not to mark the object as dirty. If the member variable 
        ///  and the new value passed in are the same, the method does nothing. Otherwise, 
        ///  if they are different, it sets the memer variable to the new value and also 
        ///  marks the object as dirty, which will come in handy at saving time.
        ///  This SetDirty method takes bools.
        /// </summary>
        /// <param name="member">the member variable's value</param>
        /// <param name="newValue">the new value </param>
        public void SetDirty(ref bool member, bool newValue)
        {
            if (member != newValue)
            {
                Dirty = true;
                member = newValue;
            }
        }

        /// <summary>
        ///  Used when setting properties on an object. This generic Set method will
        ///  determine whether or not to mark the object as dirty. If the member variable 
        ///  and the new value passed in are the same, the method does nothing. Otherwise, 
        ///  if they are different, it sets the memer variable to the new value and also 
        ///  marks the object as dirty, which will come in handy at saving time.
        ///  This SetDirty method takes DateTimes.
        /// </summary>
        /// <param name="member">the member variable's value</param>
        /// <param name="newValue">the new value </param>
        public void SetDirty(ref DateTime member, DateTime newValue)
        {
            if (member != newValue)
            {
                Dirty = true;
                member = newValue;
            }
        }

        /// <summary>
        ///  Used when setting properties on an object. This generic Set method will
        ///  determine whether or not to mark the object as dirty. If the member variable 
        ///  and the new value passed in are the same, the method does nothing. Otherwise, 
        ///  if they are different, it sets the memer variable to the new value and also 
        ///  marks the object as dirty, which will come in handy at saving time.
        ///  This SetDirty method takes decimals.
        /// </summary>
        /// <param name="member">the member variable's value</param>
        /// <param name="newValue">the new value </param>
        public void SetDirty(ref decimal member, decimal newValue)
        {
            if (member != newValue)
            {
                Dirty = true;
                member = newValue;
            }
        }

        /// <summary>
        ///  Used when setting properties on an object. This generic Set method will
        ///  determine whether or not to mark the object as dirty. If the member variable 
        ///  and the new value passed in are the same, the method does nothing. Otherwise, 
        ///  if they are different, it sets the memer variable to the new value and also 
        ///  marks the object as dirty, which will come in handy at saving time.
        /// </summary>
        /// <param name="member">the member variable's value</param>
        /// <param name="newValue">the new value </param>
        public void SetDirty<T>(T member, T newValue)
        {
            if (member.Equals(newValue))
            {
                Dirty = true;
                member = newValue;
            }
        }

        #endregion
    }

}
